/*
 * Copyright (c) 2017-present, CV4J Contributors.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.cv4j.core.filters;

import com.cv4j.core.datamodel.ByteProcessor;
import com.cv4j.core.datamodel.ColorProcessor;
import com.cv4j.core.datamodel.ImageData;
import com.cv4j.core.datamodel.ImageProcessor;
import com.cv4j.image.util.Tools;

import java.util.Optional;

/**
 * 弗洛ste抖动过滤
 * algorithm -http://en.wikipedia.org/wiki/Floyd%E2%80%93Steinberg_dithering
 * http://en.literateprograms.org/Floyd-Steinberg_dithering_(C)
 * ******Floyd Steinberg dithering*******
 * 0       0,       0*
 * 0       P     7/16*
 * 3/16, 5/16,   1/16*
 * <p>
 * 0        0           0
 * 0           0.4375
 * 0.1875, 0.3125, 0.0625
 *
 * @author dev
 * @date 2021/07/22
 */
public class FloSteDitheringFilter implements CommonFilter {
    /**
     * 内核数据
     */
    public static final float[] KERNEL_DATA = new float[]{0.1875f, 0.3125f, 0.0625f, 0.4375f};
    /**
     * 调色板
     */
    public static final int[] COLOR_PALETTE = new int[]{0, 255};

    /**
     * 过滤器
     *
     * @param src src
     * @return {@link ImageProcessor}
     */
    @Override
    public Optional<ImageProcessor> filter(ImageProcessor src) {
        ImageProcessor newSrc = src;
        if (newSrc instanceof ColorProcessor) {
            newSrc.getImage().convert2Gray();
            newSrc = src.getImage().getProcessor();
        }

        int width = src.getWidth();
        int height = src.getHeight();
        byte[] gray = ((ByteProcessor) newSrc).getGray();
        byte[] output = new byte[gray.length];
        fil(width, gray, output, height);
        ((ByteProcessor) newSrc).putGray(gray);
        gray = null;
        return Optional.ofNullable(newSrc);
    }

    private void fil(int width, byte[] gray, byte[] output, int height) {
        int tempGray = 0;
        int err = 0;
        for (int row = 0; row < height; row++) {
            int offset = row * width;
            for (int col = 0; col < width; col++) {
                tempGray = gray[offset] & 0xff;
                int cIndex = getCloseColor(tempGray);
                output[offset] = (byte) COLOR_PALETTE[cIndex];
                int er = tempGray - COLOR_PALETTE[cIndex];
                int k1 = 0;
                if (row + 1 < height && 0 < col - 1) {
                    k1 = (row + 1) * width + col - 1;
                    err = gray[k1] & 0xff;
                    err += (int) (er * KERNEL_DATA[0]);
                    gray[k1] = (byte) Tools.clamp(err);
                }

                if (width > col + 1) {
                    k1 = row * width + col + 1;
                    err = gray[k1] & 0xff;
                    err += (int) (er * KERNEL_DATA[3]);
                    gray[k1] = (byte) Tools.clamp(err);
                }

                if (height > row + 1) {
                    k1 = (row + 1) * width + col;
                    err = gray[k1] & 0xff;
                    err += (int) (er * KERNEL_DATA[1]);
                    gray[k1] = (byte) Tools.clamp(err);
                }

                if (height > row + 1 && width > col + 1) {
                    k1 = (row + 1) * width + col + 1;
                    err = gray[k1] & 0xff;
                    err += (int) (er * KERNEL_DATA[2]);
                    gray[k1] = (byte) Tools.clamp(err);
                }
                offset++;
            }
        }
    }

    /**
     * 接近的颜色
     *
     * @param gray 灰色的
     * @return int
     */
    private int getCloseColor(int gray) {
        int minDistanceSquared = 255 * 255 + 1;
        int bestIndex = 0;
        for (int i = 0; i < COLOR_PALETTE.length; i++) {
            int diff = Math.abs(gray - COLOR_PALETTE[i]);
            if (ImageData.SQRT_LUT[diff] < minDistanceSquared) {
                minDistanceSquared = ImageData.SQRT_LUT[diff];
                bestIndex = i;
            }
        }
        return bestIndex;
    }
}
